import EventHandler from './EventHandler.js'
import velocity from '../velocity.js'

const LONG_PRESS = 'LONG_PRESS'
const TAP = 'TAP'

const LONG_PRESS_TIMEOUT = 500
const TAP_TIMEOUT = 200
const DOUBLE_TAP_TIMEOUT = 300
const DOUBLE_TAP_MIN_TIME = 20

const MIN_VELOCITY = 30

const TOUCH_SLOP = 2
const SLOP_SQUARE = TOUCH_SLOP * TOUCH_SLOP

const DOUBLE_TAP_SLOP = 30
const DOUBLE_TAP_SLOP_SQUARE = DOUBLE_TAP_SLOP * DOUBLE_TAP_SLOP

/**
 * 手势识别，通过setListener函数设置回调，回调包含如下方法：
 * onScroll: 滚动
 * onTap: 单击
 * onDoubleTap: 快速双击
 * onLongPress: 长按
 * onFling: 快速滚动
 *
 * 由于就算有多根手指触摸屏幕，TouchEvent内依然只包含一组坐标，因此暂时无法实现缩放手势
 */
class Gesture {
    constructor() {
        this.downX = 0
        this.downY = 0

        this.lastX = 0
        this.lastY = 0

        this.lastMoveX = 0
        this.lastMoveY = 0

        this.handler = new EventHandler()
        this.listener = null

        this.currentDownEvent = null
        this.previousUpEvent = null
        this.inTapRegion = false
        this.inDoubleTapRegion = false
        this.inLongPress = false
        this.isDoubleTapping = false
        this.inDown = false
        this.triggerTap = false

        this.longPressHandler = () => {
            this.inLongPress = true
            this.handler.removeEvent(TAP);
            if (this.listener && this.listener.onLongPress) {
                this.listener.onLongPress()
            }
        }
        this.tapHandler = () => {
            if (this.triggerTap && this.listener && this.listener.onTap) {
                this.listener.onTap(this.currentDownEvent)
            }
        }
    }

    /**
     * 设置手势回调
     */
    setListener(listener) {
        this.listener = listener
    }

    /**
     * 在ontouchstart中调用此函数
     */
    onDown(e) {
        const x = this.getX(e)
        const y = this.getY(e)

        velocity.addEvent(velocity.DOWN, x, y)

        const hadTapMessage = this.handler.hasEvent(TAP)
        if (hadTapMessage) this.handler.removeEvent(TAP)
        if (this.currentDownEvent && this.previousUpEvent
                && hadTapMessage && this.isDoubleTap(this.currentDownEvent, this.previousUpEvent, e)) {
            this.isDoubleTapping = true;
        } else {
            this.handler.sendTimingEvent(TAP, TAP_TIMEOUT, this.tapHandler)
        }

        this.downX = this.lastX = x
        this.downY = this.lastY = y

        try {
            // throwing (n.getPageRouterAction is not a function) at first time, don't know why
            this.currentDownEvent = JSON.parse(JSON.stringify(e))
        } catch(er) {
            console.log(er)
        }

        this.inTapRegion = true
        this.inDoubleTapRegion = true
        this.inDown = true
        this.inLongPress = false
        this.triggerTap = false

        this.handler.removeEvent(LONG_PRESS)
        this.handler.sendTimingEvent(LONG_PRESS, LONG_PRESS_TIMEOUT, this.longPressHandler)
    }

    /**
     * 在ontouchmove中调用此函数
     */
    onMove(e) {
        const x = this.lastMoveX = this.getX(e)
        const y = this.lastMoveY = this.getY(e)

        velocity.addEvent(velocity.MOVE, x, y)

        if (this.inLongPress) return

        const distanceX = x - this.downX
        const distanceY = y - this.downY

        const diffX = x - this.lastX
        const diffY = y - this.lastY

        this.lastX = x
        this.lastY = y

        if (this.isDoubleTapping) {

        } else if (this.inTapRegion) {
            const distance = (diffX * diffX) + (diffY * diffY);
            if (distance > SLOP_SQUARE) {
                if (this.listener && this.listener.onScroll) {
                    this.callScroll(e, distanceX, distanceY, diffX, diffY, false)
                }
                this.inTapRegion = false
                this.handler.removeEvent(TAP);
                this.handler.removeEvent(LONG_PRESS);
            }
            if (distance > DOUBLE_TAP_SLOP_SQUARE) {
                this.inDoubleTapRegion = false;
            }
        } else { // if ((Math.abs(diffX) >= 1) || (Math.abs(diffY) >= 1)) {
            this.callScroll(e, distanceX, distanceY, diffX, diffY, false)
        }
    }

    /**
     * 在ontouchend中调用此函数
     */
    async onUp(e) {
        // up event内没有坐标数据
        velocity.addEvent(velocity.UP, this.lastMoveX, this.lastMoveY)

        this.inDown = false
        this.previousUpEvent = JSON.parse(JSON.stringify(e))
        if (this.isDoubleTapping) {
            if (this.listener && this.listener.onDoubleTap) {
                this.listener.onDoubleTap(this.currentDownEvent)
            }
        } else if (this.inLongPress) {
            this.handler.removeEvent(TAP)
            this.inLongPress = false
        }  else if (this.inTapRegion) {
            this.triggerTap = true
        } else {
            this.callScroll(e, this.lastMoveX - this.downX, this.lastMoveY - this.downY, 0, 0, true)
            if (this.listener && this.listener.onFling) {
                const v = await velocity.getVelocity()
                const vx = v.x, vy = v.y
                this.listener.onFling(vx, vy, Math.abs(vx) >= MIN_VELOCITY || Math.abs(vy) >= MIN_VELOCITY)
                velocity.clear()
            }
        }

        this.isDoubleTapping = false;
        this.handler.removeEvent(LONG_PRESS);
    }

    callScroll(e, distanceX, distanceY, diffX, diffY, up) {
        if (this.listener && this.listener.onScroll) {
            this.listener.onScroll(e, distanceX, distanceY, diffX, diffY, up)
        }
    }

    isDoubleTap(firstDown, firstUp, secondDown) {
        if (!this.inDoubleTapRegion) return false

        const deltaTime = secondDown.timestamp - firstUp.timestamp
        if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
            return false
        }

        const deltaX = this.getX(firstDown) - this.getX(secondDown)
        const deltaY = this.getY(firstDown) - this.getY(secondDown)

        return (deltaX * deltaX + deltaY * deltaY < DOUBLE_TAP_SLOP_SQUARE)
    }

    getX(e) {
        return e.touches[0].globalX
    }

    getY(e) {
        return e.touches[0].globalY
    }

    cancel() {
        this.handler.removeEvent(LONG_PRESS)
        this.handler.removeEvent(TAP)
        this.isDoubleTapping = false
        this.inDown = false
        this.inTapRegion = false
        this.inDoubleTapRegion = false
        this.inLongPress = false
        this.triggerTap = false
    }
}

export default Gesture